﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;

using MySql.Data.MySqlClient;

namespace Uber.Storage
{
    class DatabaseManager
    {
        private DatabaseServer Server;
        private Database Database;

        private Dictionary<uint, DatabaseClient> Clients;
        private int ClientStarvation;

        private Task ClientMonitor;

        private readonly Object dbLock = new Object();

        public string ConnectionString
        {
            get
            {
                MySqlConnectionStringBuilder ConnString = new MySqlConnectionStringBuilder();

                ConnString.Server = Server.Hostname;
                ConnString.Port = Server.Port;
                ConnString.UserID = Server.Username;
                ConnString.Password = Server.Password;
                ConnString.Database = Database.DatabaseName;
                ConnString.MinimumPoolSize = Database.PoolMinSize;
                ConnString.MaximumPoolSize = Database.PoolMaxSize;
                ConnString.Pooling = true;

                return ConnString.ToString();
            }
        }

        public DatabaseManager(DatabaseServer _Server, Database _Database)
        {
            Server = _Server;
            Database = _Database;

            Clients = new Dictionary<uint, DatabaseClient>();
            ClientStarvation = 0;

            SetClientAmount(Database.PoolMinSize);
        }

        public void DestroyClients()
        {
            lock (dbLock)
            {
                foreach (DatabaseClient Client in Clients.Values)
                {
                    Client.Destroy();
                }

                this.Clients = null;
                this.Clients = new Dictionary<uint, DatabaseClient>();
            }
        }

        public void DestroyDatabaseManager()
        {
            StopClientMonitor();
            DestroyClients();

            Server = null;
            Database = null;
            Clients = null;
        }

        public void StartClientMonitor()
        {
            if (ClientMonitor == null)
            {
                ClientMonitor = new Task(MonitorClients);
                ClientMonitor.Start();
            }
        }

        public void StopClientMonitor()
        {
            if (ClientMonitor != null)
            {
                ClientMonitor = null;
            }
        }

        public void MonitorClients() // Monitor clients activity, ping clients to check they are still alive
        {
            while (true)
            {
                lock (dbLock)
                {
                    List<DatabaseClient> InactiveClients = new List<DatabaseClient>();

                    foreach (DatabaseClient Client in Clients.Values)
                    {
                        if (Client.State != ConnectionState.Closed)
                        {
                            if (Client.InactiveTime >= 60)
                            {
                                InactiveClients.Add(Client);
                            }
                        }

                        if (!Client.IsAlive)
                        {
                            InactiveClients.Add(Client);
                        }
                    }

                    foreach (DatabaseClient Client in InactiveClients)
                    {
                        Client.Destroy();
                        Clients.Remove(Client.Handle);
                    }
                }

                ClientMonitor.Wait(15000);
            }
        }

        public DatabaseClient GetClient()
        {
            lock (dbLock)
            {
                List<DatabaseClient> toRemove = new List<DatabaseClient>();
                foreach (DatabaseClient Client in Clients.Values)
                {
                    if (Client.Available)
                    {
                        ClientStarvation = 0;

                        if (Client.State == ConnectionState.Broken)
                        {
                            toRemove.Add(Client); // Client is broken, remove from clients list (re-create upon starvation)
                            continue;
                        }

                        if (Client.State == ConnectionState.Closed)
                        {
                            Client.Connect(); // Client is available but no connection is present, open a connection to the MySQL server!
                        }

                        if (Client.State == ConnectionState.Open)
                        {
                            Client.Available = false;
                            Client.UpdateLastActivity();

                            return Client;
                        }
                    }
                }

                foreach (DatabaseClient Client in toRemove)
                {
                    if (Clients.ContainsKey(Client.Handle))
                    {
                        Clients.Remove(Client.Handle);
                    }
                }

                toRemove = null;

                ClientStarvation++;

                if (ClientStarvation >= ((Clients.Count + 1) / 2))
                {
                    ClientStarvation = 0;
                    SetClientAmount((uint)(Clients.Count + 1 * 1.3f));
                    return GetClient();
                }

                DatabaseClient Anonymous = new DatabaseClient(0, this);
                Anonymous.Connect();

                return Anonymous;
            }
        }

        public void SetClientAmount(uint Amount)
        {
            lock (dbLock)
            {
                if (Clients.Count == Amount)
                {
                    return;
                }

                if (Amount < Clients.Count)
                {
                    return;
                }

                for (uint i = 0; i < Amount; i++)
                {
                    DatabaseClient Client = new DatabaseClient((uint)Clients.Count + 1, this);
                    Clients.Add(Client.Handle, Client);
                }
            }
        }

        public void ReleaseClient(uint Handle)
        {
            lock (dbLock)
            {
                if (Clients.ContainsKey(Handle))
                {
                    Clients[Handle].Available = true;
                }
            }
        }
    }
}
